#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdint.h>

#include "includes/FileSystem.h"
#include "includes/Estructuras.h"
#include "includes/InterfaceFAT.h"
#include "includes/ArchivoConfig.h"


extern st_BPB_BootSector bootSector;


/**
	Nombre: solicitarRootDirectory()
	Objetivo: solilcita al PPD el cluster donde esta el rootDirectory
**/
int solicitarRootDirectory(stCluster *cluster)
{
	uint32_t totalBytes;
	int nroBloque;
	char *retorno;
	
	/* 1. Se que el BootSector + FS Info Sector + Sectores Reservados ocupan 90 bytes */
	totalBytes = 90;

	/* 2. Calculo cuanto ocupa la FAT*/
	totalBytes += bootSector.sectorPerFAT32 * bootSector.bytesPerSector * bootSector.numberFATs;
	
	/* nroBloque = donde inicia el root Directory */
	nroBloque = (totalBytes / SIZE_BLOQUE) - 1; /* porque empiezo del bloque 0 */
	
	/* 3. Solicito todos los bloques que necesita mi archivo 
		en retorno voy a tener todo el arbol incluido archivo */
	
	if(retorno = malloc(SIZE_CLUSTER) == NULL) return EXIT_ERROR_MEMORY;
	
	if(!resultado = solicitarBloques(nroBloque, 1, retorno)) return resultado;

	memcpy(cluster, retorno, sizeof(stCluster));
	
	return EXIT_SUCCESS;
}

/**
	Nombre: solicitarProximoCluster()
	objetivo: hace un envio y recv bloqueante al PPD/RAID solicitando
	el cluster + 1 recibido
**/
int solicitarProximoCluster(uint32_t nroCluster, stCluster *cluster)
{
	uint32_t nroBloque;
	char *retorno;
	
	if(retorno = malloc(SIZE_CLUSTER) == NULL) return EXIT_ERROR_MEMORY;
	
	/* Calculo que bloque se corresponderia con este nro de cluster */
	nroBloque = getNbloqueFromNcluster(nroCluster);
	
	/* Hago el pedido al PPD/RAID*/
	if(!resultado = solicitarBloques(nroBloque, 1, retorno)) return resultado;

	memcpy(cluster, retorno, sizeof(stCluster));
	
	return EXIT_SUCCESS;
}

uint32_t obtenerNroTotalDeClusters()
{
	return bootSector.totalSector32 * sectorPerCluster;
}

/**
	Nombre: obtenerClusterSgteEnFAT()
	objetivo: accede a la fat por nro de cluster = "indice" y devuelve el cluster contenido en esa ubicacion.
**/
uint32_t obtenerClusterSgteEnFAT(uint32_t nroCluster)
{
	int i = 0;
	st_FAT_Table *ptr = tablaFAT;
	
	for(i = 2; i <= nroCluster; i++) 
	{
		ptr = ptr->next;	
	}
	
	return (uint32_t)ptr->nroCluster;
}

/**
	Nombre: armarListadoDirectorios()
	objetivo: dado un cluster arma la lista de entradas FAT st_List_FAT_Entries
**/
int armarListadoDirectorios(stCluster *cluster, st_List_FAT_Entries *list_FAT_Entries)
{
	st_FAT_Entry entradaAux;
	st_List_FAT_Entries * directory = NULL;
	st_List_FAT_Entries * ptr;
	st_List_FAT_Entries * ptrAnt=NULL;
	
	
	for(i =0; i < sizeof(stCluster); i += sizeof(st_FAT_Entry))
	{
		memcpy(&entradaAux, cluster+i, sizeof(st_FAT_Entry));
		
		if (entradaAux.longDirEntry.sequenceNro == 0)
		{
			 memcpy(list_FAT_Entries, directory, sizeof(list_FAT_Entries));
			 return EXIT_SUCCESS;
		}
		
		ptr = (st_List_FAT_Entries *)malloc(sizeof(st_List_FAT_Entries));
		memcpy( &(ptr->fatEntry), &entradaAux, sizeof(st_List_FAT_Entries));

		if(ptrAnt!=NULL)
		{
			ptrAnt->next=ptr;
		}
		else
		{
			directory = ptr;
		}		
		ptr->next = NULL;
		ptrAnt = ptr;
	}
	return EXIT_SUCCESS;
}


/**
	Nombre: obtenerArchivoDelListado()
	objetivo: dado un listado de entradas FAT lo recorre buscando 
	el archivo recibido en path y retorna solo la entrada del archivo
**/
int obtenerClusterDelArchivoFromList(st_List_FAT_Entries *dir, st_FAT_Entry *retorno, const char *path)
{
	int nroBloque;
	st_FAT_Entry *temp;
	st_FAT_Entry fileEntry;
	char *running, *start;
	char *token;
	start = running = strdup(path);
	uint32_t nroCluster;
	
	token = strsep (&running, "/");
	while((token = strsep (&running, "/")))
	{
		/* Primero necesito la entrada correcta del archivo */
		buscarNombre(&temp, dir, token);

		if (temp == NULL)
		{
			destroyMemoriaLista(dir);
			free(start);
			return SIN_RESULTADOS;
		}
		memcpy(&fileEntry, temp, sizeof(st_FAT_Entry));
		destroyMemoriaLista(dir);
		
		/* una vez que la tengo, necesito la direccion del primer cluster donde empiezan sus datos */
		nroCluster = calcularNroClusterToData(&fileEntry.shortDirEntry) + getNroCluster_RootDirectory()-2;
	}
	return nroCluster;
}

/**
	nombre: buscarNombre()
	objetivo: recorre el listado de directorios y retorna el q coincide con name
**/
int buscarNombre(st_FAT_Entry *temp, const st_List_FAT_Entries *dir, char *name)
{
	st_List_FAT_Entries *p = dir;
	
	while( p!=NULL)
	{
		if( strlen(name)!=0 && strncmp(p->fatEntry.shortDirEntry.name, name, strlen(name))==0)
		{
			 memcpy(temp, &p->fatEntry, sizeof(st_FAT_Entry));
			 return EXIT_SUCCESS;
		}
		p=p->next;
	}
	temp = NULL;
	return EXIT_SUCCESS;
}


uint32_t calcularNroClusterToData(t_fat_file_data_entry * fileEntry )
{
	// .firstClusterHigh = 0xAB
	// .firstClusterLow  = 0xCD
	// return 0xABCD
	uint32_t firstCluster;

	firstCluster = (uint32_t)(fileEntry->firstClusterHigh); // Deberia hacer que 0xAB sea 0x00AB
	firstCluster = firstCluster << 8; // Pongo los bits altos en la posicion que corresponden 0xAB00
	firstCluster = firstCluster | (uint32_t) (fileEntry->firstClusterLow); //Convierto el Low a uint32 y lo OR'eo bit a bit
	// 0xAB00  1010 1101 0000 0000
	// 0x00CD  0000 0000 1100 1101
	//----------------------------
	// 0xABCD  1010 1101 1100 1101

	//TODO: Ver si con la suma funciona tambien
	return firstCluster;
}

/**
	Nombre: getNroCluster_RootDirectory()
	Objetivo: devuelve el numero de cluster del rootDirectory
	TODO: preguntar si puedo usar mejor el campo BPB_RootClus ???? 
**/
uint32_t getNroCluster_RootDirectory()
{
	return ((uint32_t) bootSector.reservedSectorCount + (uint32_t)bootSector.numberFATs * bootSector.sectorPerFAT32)/bootSector.sectorPerCluster;
}


void destroyMemoriaLista(st_List_FAT_Entries *lista)
{
	st_List_FAT_Entries *p;
	while(lista!=NULL)
	{
		p=lista->next;
		free(lista);
		lista=p;
	}
}